package tcpclient.v2;

import frame.laxcen.BaseFrame;
import frame.laxcen.collection.ConstPair;
import frame.laxcen.request.IRequest;
import frame.laxcen.response.BaseResponse;
import frame.laxcen.response.Response_0001_12;
import frame.laxcen.util.FrameUtil;
import frame.util.ByteBufferUtil;
import frame.util.FlowUtil;
import frame.util.Ref;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.function.Predicate;
import java.util.stream.Stream;

// 连接读写器的TCP客户端
public class RFIDClient {

    // 控制 RFIDClient 的异步读回调的执行状态
    private volatile boolean isRunning = false;

    // 描述 RFIDClient 对服务器的连接
    private final AsynchronousSocketChannel channel;

    // 缓存从服务器中读取到的字节流
    private final ByteBuffer frameBuffer;

    // 阻塞的有界队列 用于缓存已经解析出的数据帧
    private final LinkedBlockingQueue<BaseFrame> queue;

    // 构造一个 RFIDClient
    // param: maxFrameSize 配置缓存字节流的大小 必须按照通信协议设置该值
    //                     如果小于协议规定的最大帧长度 则会由于无法完整解析一帧数据 造成一直触发缓冲回收造成死循环
    // param: maxQueueSize 配置阻塞队列的大小
    // throw: 当无法创建异步通道时会抛出IO异常
    public RFIDClient(int maxFrameSize, int maxQueueSize) throws IOException {

        this.channel = AsynchronousSocketChannel.open();

        this.frameBuffer = ByteBuffer.allocate(maxFrameSize);

        this.queue = new LinkedBlockingQueue<>(maxQueueSize);
    }

    // 尝试在3秒钟内连接读写器 并在连接成功后注册读回调
    // param: address 服务器IP地址及端口号
    // return: 连接成功返回true 连接失败返回false
    public boolean tryConnect(InetSocketAddress address) {

        return tryConnect(address, true, 3000);
    }

    // 尝试在3秒钟内连接读写器 并根据设置决定是否在连接成功后注册读回调
    // param: address 服务器IP地址及端口号
    // param: readyForReceive 配置是否立即注册读回调
    // return: 连接成功返回true 连接失败返回false
    public boolean tryConnect(InetSocketAddress address, boolean readyForReceive) {

        return tryConnect(address, readyForReceive, 3000);
    }

    // 尝试在若干时间内连接读写器 并在连接成功后注册读回调
    // param: address 服务器IP地址及端口号
    // param: millisecond 等待的毫秒数
    // return: 连接成功返回true 连接失败返回false
    public boolean tryConnect(InetSocketAddress address, long millisecond) {

        return tryConnect(address, true, millisecond);
    }

    // 尝试在若干时间内连接读写器 并根据设置决定是否在连接成功后注册读回调
    // param: address 服务器IP地址及端口号
    // param: millisecond 等待的毫秒数
    // param: readyForReceive 配置是否立即注册读回调
    // return: 连接成功返回true 连接失败返回false
    public boolean tryConnect(InetSocketAddress address, boolean readyForReceive, long millisecond) {

        try {

            channel.connect(address).get(millisecond, TimeUnit.MILLISECONDS);

            // 绑定回调函数 等待读写器返回数据
            if(readyForReceive) registerReadCallback();

        } catch (Exception e) { return false; }

        return true;
    }

    // 在异步通道上注册读回调事件
    public void registerReadCallback() {

        if(!isRunning) {

            isRunning = true;

            channel.read(frameBuffer, this, new CallBackForRead());
        }
    }

    // 尝试在若干时间内向读写器发送指令
    // param: frame 某指令类
    // param: millisecond 等待的毫秒数
    // return: 发送成功返回true 发送失败返回false
    public <T extends IRequest> boolean trySend(T frame, long millisecond) {

        try {

            channel.write(ByteBuffer.wrap(frame.getFrameBytes())).get(millisecond, TimeUnit.MILLISECONDS);

        } catch (Exception e) { return false; }

        return true;
    }

    // 尝试关闭与读写器之间的连接 并正常中断读回调
    // return: 关闭成功返回true 关闭失败返回false
    public boolean tryClose() {

        try {

            channel.close();

            isRunning = false;

        } catch (Exception e) { return false; }

        return true;
    }

    // 在若干时间内等待任意一条反馈消息 如果提前获取到则提前返回 否则阻塞直到超时
    // param: millisecond 等待的毫秒数
    // return: 数据帧的引用 如果没有获取到数据 则指向 null
    public Ref<BaseFrame> waitForResponse(long millisecond) {

        List<BaseFrame> response = waitForResponseList(millisecond, (ignored) -> true, 1);

        return response.size() >= 1 ? Ref.of(response.get(0)) : Ref.empty();
    }

    // 在若干时间内等待某一种反馈消息 如果提前获取到则提前返回 否则阻塞直到超时 在中间获得的其他类型的反馈消息会被丢弃
    // param: millisecond 等待的毫秒数
    // param: type 等待该Response类的数据
    // return: 数据帧的引用 如果没有获取到数据 则返回空引用
    public <T extends BaseResponse> Ref<T> waitForResponse(long millisecond, Class<T> responseType) {

        List<BaseFrame> response = waitForResponseList(millisecond, (frame) -> frame.isResponse(responseType), 1);

        return response.size() >= 1 ? response.get(0).castToResponse(responseType) : Ref.empty();
    }

    // 返回在若干事件内收到的全部反馈消息 并按照主动上传标志进行分类 不会提前返回 会阻塞直到超时
    // param: millisecond 等待的毫秒数
    // param: isNotUpload 保存非主动上传反馈消息列表的引用 如果获取了该类数据 会更新该引用
    // param: isUpload 保存主动上传反馈消息列表的引用 如果获取了该类数据 会更新该引用
    public void waitForResponseListSplitByUploadFlag(long millisecond, Ref<List<BaseFrame>> isNotUpload, Ref<List<BaseFrame>> isUpload) {

        Predicate<BaseFrame> isUploadFrame = (frame) -> frame.CW.IsUpload;

        waitForSplitResponseList(millisecond, isNotUpload, Integer.MAX_VALUE, isUpload, Integer.MAX_VALUE, isUploadFrame.negate());
    }

    // 返回在若干事件内收到的全部反馈消息 并按照某种方法进行分类 不会提前返回 会阻塞直到超时
    // 可以限制每种类型消息获得的最大数量 超出该限制的数据会被丢弃
    // param: millisecond 等待的毫秒数
    // param: trueResult 保存分类函数结果为true的结果的列表的引用 如果获取了该类数据 会更新该引用
    // param: limitOfTrueResult 分类函数结果为true的结果的列表允许容纳元素的最大值
    // param: falseResult 保存分类函数结果为false的结果的列表的引用 如果获取了该类数据 会更新该引用
    // param: limitOfFalseResult 分类函数结果为false的结果的列表允许容纳元素的最大值
    // param: predicate 分类函数
    public void waitForSplitResponseList(long millisecond, Ref<List<BaseFrame>> trueResult, int limitOfTrueResult, Ref<List<BaseFrame>> falseResult, int limitOfFalseResult, Predicate<BaseFrame> predicate) {

        List<BaseFrame> trueList = new LinkedList<>(), falseList = new LinkedList<>();

        while(millisecond > 0) {

            final long waitTime = millisecond, begin = System.currentTimeMillis();

            Ref<BaseFrame> ref = Ref.empty();

            FlowUtil.silent(() -> ref.swap(queue.poll(waitTime, TimeUnit.MILLISECONDS)));

            millisecond -= System.currentTimeMillis() - begin;

            if(ref.isPresent()) FrameUtil.dispatch(ref.get(), trueList, falseList, predicate);

            while(trueList.size() > limitOfTrueResult) trueList.remove(trueList.size() - 1);

            while(falseList.size() > limitOfFalseResult) falseList.remove(falseList.size() - 1);

            if(trueList.size() >= limitOfTrueResult && falseList.size() >= limitOfFalseResult) break;
        }

        if(trueList.size() > 0) trueResult.swap(trueList);

        if(falseList.size() > 0) falseResult.swap(falseList);
    }

    // 返回在若干事件内收到的全部反馈消息 不会提前返回 会阻塞直到超时
    // param: millisecond 等待的毫秒数
    // return: 包含全部反馈消息的列表 如果没有收到消息则为空列表
    public List<BaseFrame> waitForResponseList(long millisecond) {

        return waitForResponseList(millisecond, (ignored) -> true, Integer.MAX_VALUE);
    }

    // 返回在若干事件内收到的全部反馈消息 并使用某种方式进行过滤
    // 可以限制收到消息的数量 在达到该值后返回 或阻塞直到超时
    // param: millisecond 等待的毫秒数
    // param: predicate 过滤函数
    // param: limit 限制数量
    // return: 包含过滤后反馈消息的列表 如果没有收到消息则为空列表
    public List<BaseFrame> waitForResponseList(long millisecond, Predicate<BaseFrame> predicate, int limit) {

        List<BaseFrame> response = new LinkedList<>();

        while(millisecond > 0) {

            final long waitTime = millisecond, begin = System.currentTimeMillis();

            Ref<BaseFrame> ref = Ref.empty();

            FlowUtil.silent(() -> ref.swap(queue.poll(waitTime, TimeUnit.MILLISECONDS)));

            millisecond -= System.currentTimeMillis() - begin;

            if(ref.isPresent()) response.add(ref.get());

            if(response.size() > 0) response.removeIf(predicate.negate());

            if(response.size() >= limit) break;
        }

        return response;
    }

    // 用于TCP连接通道异步读的回调类
    private static class CallBackForRead implements CompletionHandler<Integer, RFIDClient> {

        // 异步读完成后执行该方法
        @Override
        public void completed(Integer ignored, RFIDClient client) {

            byte[] buffer = ByteBufferUtil.readAndClear(client.frameBuffer);

            // 已解析出的帧数据列表 和 剩余数据的偏移
            ConstPair<List<BaseFrame>, Integer> pair = FrameUtil.disjoint(buffer);

            // 将剩余的不完整的帧数据重新写回缓冲区
            FrameUtil.recycle(client.frameBuffer, buffer, pair.getValue2());

            // 分离心跳包和非心跳包
            List<BaseFrame> isHeartBeat = new ArrayList<>(), isNotHeartBeat = new ArrayList<>();

            FrameUtil.split(pair.getValue1(), isHeartBeat, isNotHeartBeat, Response_0001_12.isHeartBeat);

            // 处理心跳包
            if(isHeartBeat.size() > 0) {

                Stream<Response_0001_12> heartBeatFrames = isHeartBeat.stream().map((frame) -> frame.castToResponse(Response_0001_12.class)).filter(Ref::isPresent).map(Ref::get);

                heartBeatFrames.forEach((obj) -> client.trySend(obj.reflectToRequest(), 100));
            }

            // 处理非心跳包
            isNotHeartBeat.forEach((frame) -> FlowUtil.silent(() -> client.queue.put(frame)));

            // 继续异步等待新数据到来
            if(client.isRunning) client.channel.read(client.frameBuffer, client, new CallBackForRead());
        }

        // 异步读执行中发生异常后执行该方法
        @Override
        public void failed(Throwable exc, RFIDClient client) { exc.printStackTrace(); }
    }
}
